/**
 * Copyright (C) 2014 Paul Cech
 * <p>
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * <p>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p>
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.eazegraph.lib.charts;

import ohos.agp.components.AttrSet;
import ohos.agp.components.Component;
import ohos.agp.components.ComponentContainer;
import ohos.agp.render.Canvas;
import ohos.agp.utils.Matrix;
import ohos.agp.utils.Point;
import ohos.app.Context;
import ohos.multimodalinput.event.TouchEvent;
import org.eazegraph.lib.ValueAnimator;
import org.eazegraph.lib.models.BaseModel;
import org.eazegraph.lib.utils.AttrUtils;
import org.eazegraph.lib.utils.LogUtil;
import org.eazegraph.lib.utils.Utils;

import java.util.List;


/**
 * This is the main chart class and should be inherited by every graph. This class provides some general
 * methods and variables, which are needed and used by every type of chart.
 */
public abstract class BaseChart extends ComponentContainer implements Component.LayoutRefreshedListener, Component.DrawTask, Component.TouchEventListener {
    /**
     * * The constant DEF_LEGEND_HEIGHT
     */
    public static final float DEF_LEGEND_HEIGHT = 58.f;

    /**
     * * The constant DEF_LEGEND_COLOR
     */
    public static final int DEF_LEGEND_COLOR = 0xFF898989;

    /**
     * * The constant DEF_LEGEND_TEXT_SIZE
     */
    public static final float DEF_LEGEND_TEXT_SIZE = 12.f;

    /**
     * * The constant DEF_ANIMATION_TIME
     */
    public static final int DEF_ANIMATION_TIME = 2000;

    /**
     * * The constant DEF_SHOW_DECIMAL
     */
    public static final boolean DEF_SHOW_DECIMAL = false;

    /**
     * * The constant DEF_EMPTY_DATA_TEXT
     */
    public static final String DEF_EMPTY_DATA_TEXT = "No Data available";


    /**
     * * The constant TAG
     */
    private static final String TAG = "BaseChart";

    /**
     * The constant Base chart eg legend height
     */
    private final String BaseChart_egLegendHeight = "egLegendHeight";

    /**
     * * The constant BaseChart_egLegendTextSize
     */
    private final String BaseChart_egLegendTextSize = "egLegendTextSize";

    /**
     * This is called during layout when the size of this view has changed. If
     * you were just added to the view hierarchy, you're called with the old
     * values of 0.
     */
    private final String BaseChart_egAnimationTime = "egAnimationTime";
    /**
     * * The constant BaseChart_egShowDecimal
     */
    private final String BaseChart_egShowDecimal = "egShowDecimal";
    /**
     * * The constant BaseChart_egLegendColor
     */
    private final String BaseChart_egLegendColor = "egLegendColor";
    /**
     * * The constant BaseChart_egEmptyDataText
     */
    private final String BaseChart_egEmptyDataText = "egEmptyDataText";
    /**
     * The constant M graph
     */
    protected Graph mGraph;
    /**
     * The constant M graph overlay
     */
    protected GraphOverlay mGraphOverlay;
    /**
     * The constant M legend
     */
    protected Legend mLegend;
    /**
     * The constant M height
     */
    protected int mHeight;
    /**
     * The constant M width
     */
    protected int mWidth;
    /**
     * The constant M graph width
     */
    protected int mGraphWidth;
    /**
     * The constant M graph height
     */
    protected int mGraphHeight;
    /**
     * The constant M legend width
     */
    protected float mLegendWidth;
    /**
     * The constant M legend height
     */
    protected float mLegendHeight;
    /**
     * The constant M legend text size
     */
    protected float mLegendTextSize;
    /**
     * The constant M legend color
     */
    protected int mLegendColor;
    /**
     * The constant M left padding
     */
    protected int mLeftPadding;
    /**
     * The constant M top padding
     */
    protected int mTopPadding;
    /**
     * The constant M right padding
     */
    protected int mRightPadding;
    /**
     * The constant M bottom padding
     */
    protected int mBottomPadding;
    /**
     * The constant M empty data text
     */
    protected String mEmptyDataText;
    /**
     * The constant M max font height
     */
    protected float mMaxFontHeight;
    /**
     * The constant M legend top padding
     */
    protected float mLegendTopPadding = Utils.dpToPx(getContext(), 4.f);
    /**
     * The constant M show decimal
     */
    protected boolean mShowDecimal;
    /**
     * The constant M reveal animator
     */
    protected ValueAnimator mRevealAnimator = null;
    /**
     * The constant M reveal value
     */
    protected float mRevealValue = 1.0f;

    /**
     * The constant M animation time
     */
    protected int mAnimationTime = 1000;

    /**
     * The constant M started animation
     */
    protected boolean mStartedAnimation = false;

    /**
     * Simple constructor to use when creating a view from code.
     *
     * @param context The Context the view is running in, through which it can                access the current theme, resources, etc.
     */
    protected BaseChart(Context context) {
        super(context);

        mLegendHeight = Utils.dpToPx(getContext(), DEF_LEGEND_HEIGHT);
        mLegendTextSize = Utils.dpToPx(getContext(), DEF_LEGEND_TEXT_SIZE);
        mLegendColor = DEF_LEGEND_COLOR;
        mAnimationTime = DEF_ANIMATION_TIME;
        mShowDecimal = DEF_SHOW_DECIMAL;
        mEmptyDataText = DEF_EMPTY_DATA_TEXT;
    }

    /**
     * Constructor that is called when inflating a view from XML. This is called
     * when a view is being constructed from an XML file, supplying attributes
     * that were specified in the XML file. This version uses a default style of
     * 0, so the only attribute values applied are those in the Context's Theme
     * and the given AttributeSet.
     * <p/>
     * <p/>
     * The method onFinishInflate() will be called after all children have been
     * added.
     *
     * @param context The Context the view is running in, through which it can access the current theme, resources, etc.
     * @param attrs   The attributes of the XML tag that is inflating the view.
     */
    public BaseChart(Context context, AttrSet attrs) {
        super(context, attrs);
        mLegendHeight = AttrUtils.getDimensionFromAttr(attrs, BaseChart_egLegendHeight, (int) Utils.dpToPx(getContext(), DEF_LEGEND_HEIGHT));
        mLegendTextSize = AttrUtils.getDimensionFromAttr(attrs, BaseChart_egLegendTextSize, (int) Utils.dpToPx(getContext(), DEF_LEGEND_TEXT_SIZE));
        mAnimationTime = AttrUtils.getIntFromAttr(attrs, BaseChart_egAnimationTime, DEF_ANIMATION_TIME);
        mShowDecimal = AttrUtils.getBooleanFromAttr(attrs, BaseChart_egShowDecimal, DEF_SHOW_DECIMAL);
        mLegendColor = AttrUtils.getColorFromAttr(attrs, BaseChart_egLegendColor, DEF_LEGEND_COLOR);
        mEmptyDataText = AttrUtils.getStringFromAttr(attrs, BaseChart_egEmptyDataText, "");

        if (mEmptyDataText == null) {
            mEmptyDataText = DEF_EMPTY_DATA_TEXT;
        }

        setLayoutRefreshedListener(this);
        addDrawTask(this);
        setTouchEventListener(this);
    }

    /**
     * On draw *
     *
     * @param component component
     * @param canvas    canvas
     */
    @Override
    public void onDraw(Component component, Canvas canvas) {
        LogUtil.info(TAG, "BaseChart onDraw");
    }

    /**
     * Implement this method to handle touch screen motion events.
     * <p/>
     * If this method is used to detect click actions, it is recommended that
     * the actions be performed by implementing and calling
     *
     * </ul>
     *
     * @param component  component
     * @param touchEvent touch event
     * @return the boolean
     */
    @Override
    public boolean onTouchEvent(Component component, TouchEvent touchEvent) {
        LogUtil.info(TAG, "BaseChart onTouchEvent");
        return false;
    }

    /**
     * Returns the current height of the legend view
     *
     * @return Legend view height
     */
    public float getLegendHeight() {
        return mLegendHeight;
    }

    /**
     * Returns the text size which is used by the legend.
     *
     * @return Size of the legend text.
     */
    public float getLegendTextSize() {
        return mLegendTextSize;
    }

    /**
     * Returns the animation time in milliseconds.
     *
     * @return Animation time.
     */
    public int getAnimationTime() {
        return mAnimationTime;
    }

    /**
     * Sets the animation time in milliseconds.
     *
     * @param _animationTime Animation time in milliseconds.
     */
    public void setAnimationTime(int _animationTime) {
        mAnimationTime = _animationTime;
    }

    /**
     * Is show decimal boolean
     *
     * @return the boolean
     */
    public boolean isShowDecimal() {
        return mShowDecimal;
    }

    /**
     * Set show decimal *
     *
     * @param _showDecimal show decimal
     */
    public void setShowDecimal(boolean _showDecimal) {
        mShowDecimal = _showDecimal;
        invalidate();
    }

    /**
     * Get legend color int
     *
     * @return the int
     */
    public int getLegendColor() {
        return mLegendColor;
    }

    /**
     * Set legend color *
     *
     * @param _legendColor legend color
     */
    public void setLegendColor(int _legendColor) {
        mLegendColor = _legendColor;
    }

    /**
     * Get empty data text string
     *
     * @return the string
     */
    public String getEmptyDataText() {
        return mEmptyDataText;
    }

    /**
     * Set empty data text *
     *
     * @param _emptyDataText empty data text
     */
    public void setEmptyDataText(String _emptyDataText) {
        mEmptyDataText = _emptyDataText;
    }

    /**
     * Returns the datasets which are currently inserted.
     *
     * @return the datasets
     */
    public abstract List<? extends BaseModel> getData();

    /**
     * Resets and clears the data object.
     */
    public abstract void clearChart();

    /**
     * Should be called when the dataset changed and the graph should update and redraw.
     * Graph implementations might overwrite this method to do more work than just call onDataChanged()
     */
    public void update() {
        onDataChanged();
    }

    /**
     * Starts the chart animation.
     */
    public void startAnimation() {
        if (mRevealAnimator != null) {
            mStartedAnimation = true;
            mRevealAnimator.setDuration(mAnimationTime);
            mRevealAnimator.start();
        }
    }

    /**
     * This is called during layout when the size of this view has changed. If
     * you were just added to the view hierarchy, you're called with the old
     * values of 0.
     *
     * @param component component
     */
    @Override
    public void onRefreshed(Component component) {
        mWidth = getWidth();
        mHeight = getHeight();

        mLeftPadding = getPaddingLeft();
        mTopPadding = getPaddingTop();
        mRightPadding = getPaddingRight();
        mBottomPadding = getPaddingBottom();
        LogUtil.info(TAG, "BaseChart onRefreshed: " + "mLeftPadding: " + mLeftPadding + " mTopPadding: " + mTopPadding
                + "  mRightPadding: " + mRightPadding + " mBottomPadding: " + mBottomPadding + " mWidth : " + mWidth + " mHeight: " + mHeight + " mLegendHeight: " + mLegendHeight);
        mGraphWidth = mWidth - mRightPadding - mLeftPadding;
        mGraphHeight = (int) (mHeight - mLegendHeight - mBottomPadding - mTopPadding);
        if (((mHeight - mLegendHeight - mBottomPadding) - mTopPadding) >= 0 && (mWidth - mRightPadding - mLeftPadding) >= 0) {
            mGraph.setComponentPosition(mLeftPadding, mTopPadding, mWidth - mRightPadding, (int) (mHeight - mLegendHeight - mBottomPadding));
        } else {
            mGraph.setComponentPosition(mLeftPadding, mTopPadding, mLeftPadding, mTopPadding);
        }
        mGraph.onRefreshed(mGraph);

        if (((mHeight - mLegendHeight - mBottomPadding) - mTopPadding) >= 0 && (mWidth - mRightPadding - mLeftPadding) >= 0) {
            mGraphOverlay.setComponentPosition(mLeftPadding, mTopPadding, mWidth - mRightPadding, (int) (mHeight - mLegendHeight - mBottomPadding));
        } else {
            mGraphOverlay.setComponentPosition(mLeftPadding, mTopPadding, mLeftPadding, mTopPadding);
        }

        mLegendWidth = mWidth - mRightPadding - mLeftPadding;
        mLegendHeight = mLegendHeight;
        if (mLegendHeight >= 0 && (mWidth - mRightPadding - mLeftPadding) >= 0) {
            mLegend.setComponentPosition(mLeftPadding, (int) (mHeight - mLegendHeight - mBottomPadding), mWidth - mRightPadding, mHeight - mBottomPadding);
        } else {
            mLegend.setComponentPosition(mLeftPadding, mHeight - mBottomPadding, mLeftPadding, mHeight - mBottomPadding);
        }
        mLegend.onRefreshed(mLegend);
        LogUtil.info(TAG, "BaseChart onRefreshed: " + "mLeftPadding: " + mLeftPadding + " mTopPadding: " + mTopPadding
                + "  mRightPadding: " + mRightPadding + " mBottomPadding: " + mBottomPadding + " mWidth : " + mWidth + " mHeight: " + mHeight + " mLegendHeight: " + mLegendHeight);
        setLayoutRefreshedListener(null);                
    }

    /**
     * This is the main entry point after the graph has been inflated. Used to initialize the graph
     * and its corresponding members.
     */
    protected void initializeGraph() {
        mGraph = new Graph(getContext());
        addComponent(mGraph);
        mGraphOverlay = new GraphOverlay(getContext());
        addComponent(mGraphOverlay);
        mLegend = new Legend(getContext());
        addComponent(mLegend);
        mLegend.setBackground(getBackgroundElement());        
    }

    /**
     * Should be called after new data is inserted. Will be automatically called, when the view dimensions
     * has changed.
     */
    protected void onDataChanged() {
        invalidateGlobal();
    }

    /**
     * Invalidates graph and legend and forces them to be redrawn.
     */
    protected final void invalidateGlobal() {
        mGraph.invalidate();
        mGraphOverlay.invalidate();
        mLegend.invalidate();
    }

    /**
     * Invalidate graph
     */
    protected final void invalidateGraph() {
        mGraph.invalidate();
    }

    /**
     * Invalidate graph overlay
     */
    protected final void invalidateGraphOverlay() {
        mGraphOverlay.invalidate();
    }

    /**
     * Invalidate legend
     */
    protected final void invalidateLegend() {
        mLegend.invalidate();
    }

    /**
     * On graph draw *
     *
     * @param _Canvas canvas
     */
    protected void onGraphDraw(Canvas _Canvas) {
    }

    /**
     * On graph overlay draw *
     *
     * @param _Canvas canvas
     */
    protected void onGraphOverlayDraw(Canvas _Canvas) {

    }

    /**
     * On legend draw *
     *
     * @param _Canvas canvas
     */
    protected void onLegendDraw(Canvas _Canvas) {

    }

    /**
     * On graph overlay touch event boolean
     *
     * @param _Event event
     * @return the boolean
     */
    protected boolean onGraphOverlayTouchEvent(TouchEvent _Event) {
        LogUtil.info(TAG, "BaseChart onGraphOverlayTouchEvent");
        return false;
    }

    /**
     * On graph size changed *
     *
     * @param width  width
     * @param height height
     * @param oldw   oldw
     * @param oldh   oldh
     */
    protected void onGraphSizeChanged(int width, int height, int oldw, int oldh) {

    }

    /**
     * On graph overlay size changed *
     *
     * @param width  width
     * @param height height
     * @param oldw   oldw
     * @param oldh   oldh
     */
    protected void onGraphOverlaySizeChanged(int width, int height, int oldw, int oldh) {

    }

    /**
     * On legend size changed *
     *
     * @param width  width
     * @param height height
     * @param oldw   oldw
     * @param oldh   oldh
     */
    protected void onLegendSizeChanged(int width, int height, int oldw, int oldh) {

    }

    /**
     * Graph
     */
    protected class Graph extends Component implements DrawTask, LayoutRefreshedListener, TouchEventListener {
        /**
         * The constant M rotation
         */
        private float mRotation = 0;
        /**
         * The constant M transform
         */
        private Matrix mTransform = new Matrix();
        /**
         * The constant M pivot
         */
        private Point mPivot = new Point();

        /**
         * Simple constructor to use when creating a view from code.
         *
         * @param context The Context the view is running in, through which it can                access the current theme, resources, etc.
         */
        private Graph(Context context) {
            super(context);
            Graph.this.addDrawTask(Graph.this);
            Graph.this.setLayoutRefreshedListener(Graph.this);
            Graph.this.setTouchEventListener(Graph.this);
        }

        /**
         * Enable hardware acceleration (consumes memory)
         */
        public void accelerate() {
        }

        /**
         * Disable hardware acceleration (releases memory)
         */
        public void decelerate() {
        }

        /**
         * Implement this to do your drawing.
         *
         * @param component component
         * @param canvas    the canvas on which the background will be drawn
         */
        @Override
        public void onDraw(Component component, Canvas canvas) {
            LogUtil.info(TAG, "Graph onDraw");
            onGraphDraw(canvas);
        }

        /**
         * This is called during layout when the size of this view has changed. If
         * you were just added to the view hierarchy, you're called with the old
         * values of 0.
         *
         * @param component component
         */
        @Override
        public void onRefreshed(Component component) {
            LogUtil.info(TAG, "Graph onRefreshed" + " getWidth: "
                    + component.getWidth() + " getHeight: " + component.getHeight());
            onGraphSizeChanged(mGraphWidth, mGraphHeight, mGraphWidth, mGraphHeight);

        }

        /**
         * Rotate to *
         *
         * @param pieRotation pie rotation
         */
        public void rotateTo(float pieRotation) {
            mRotation = pieRotation;
            setRotation(pieRotation);
        }

        /**
         * Set pivot *
         *
         * @param pointX pointX
         * @param pointY pointY
         */
        public void setPivot(float pointX, float pointY) {
            mPivot = new Point(pointX, pointY);
            setPivotX(pointX);
            setPivotY(pointY);
        }

        /**
         * On touch event boolean
         *
         * @param component  component
         * @param touchEvent touch event
         * @return the boolean
         */
        @Override
        public boolean onTouchEvent(Component component, TouchEvent touchEvent) {
            LogUtil.info(TAG, "Graph touchEvent");
            return false;
        }
    }

    /**
     * Graph overlay
     */
    protected class GraphOverlay extends Component implements DrawTask, Component.LayoutRefreshedListener, Component.TouchEventListener {
        /**
         * Simple constructor to use when creating a view from code.
         *
         * @param context The Context the view is running in, through which it can                access the current theme, resources, etc.
         */
        private GraphOverlay(Context context) {
            super(context);
            GraphOverlay.this.addDrawTask(GraphOverlay.this);
            GraphOverlay.this.setLayoutRefreshedListener(GraphOverlay.this);
            GraphOverlay.this.setTouchEventListener(GraphOverlay.this);
        }

        /**
         * Enable hardware acceleration (consumes memory)
         */
        public void accelerate() {

        }

        /**
         * Disable hardware acceleration (releases memory)
         */
        public void decelerate() {

        }

        /**
         * Implement this to do your drawing.
         *
         * @param component component
         * @param canvas    the canvas on which the background will be drawn
         */
        @Override
        public void onDraw(Component component, Canvas canvas) {
            LogUtil.info(TAG, "GraphOverlay onDraw");
            onGraphOverlayDraw(canvas);
        }

        /**
         * This is called during layout when the size of this view has changed. If
         * you were just added to the view hierarchy, you're called with the old
         * values of 0.
         *
         * @param component component
         */
        @Override
        public void onRefreshed(Component component) {
            LogUtil.info(TAG, "GraphOverlay onRefreshed" + " getWidth: "
                    + component.getWidth() + " getHeight: " + component.getHeight());
            onGraphOverlaySizeChanged(component.getWidth(), component.getHeight(), component.getWidth(), component.getHeight());
        }

        /**
         * Implement this method to handle touch screen motion events.
         * <p/>
         * If this method is used to detect click actions, it is recommended that
         * the actions be performed by implementing and calling
         *
         * </ul>
         *
         * @param component  component
         * @param touchEvent touch event
         * @return the boolean
         */
        @Override
        public boolean onTouchEvent(Component component, TouchEvent touchEvent) {
            LogUtil.info(TAG, "GraphOverlay onTouchEvent");
            return onGraphOverlayTouchEvent(touchEvent);
        }


    }

    /**
     * Legend
     */
    protected class Legend extends Component implements DrawTask, Component.LayoutRefreshedListener, Component.TouchEventListener {
        /**
         * Simple constructor to use when creating a view from code.
         *
         * @param context The Context the view is running in, through which it can                access the current theme, resources, etc.
         */
        private Legend(Context context) {
            super(context);
            Legend.this.addDrawTask(Legend.this);
            Legend.this.setLayoutRefreshedListener(Legend.this);
            Legend.this.setTouchEventListener(Legend.this);
        }

        /**
         * Enable hardware acceleration (consumes memory)
         */
        public void accelerate() {
        }

        /**
         * Disable hardware acceleration (releases memory)
         */
        public void decelerate() {
        }

        /**
         * Implement this to do your drawing.
         *
         * @param component component
         * @param canvas    the canvas on which the background will be drawn
         */
        @Override
        public void onDraw(Component component, Canvas canvas) {
            LogUtil.info(TAG, "Legend onDraw");
            onLegendDraw(canvas);
        }

        /**
         * This is called during layout when the size of this view has changed. If
         * you were just added to the view hierarchy, you're called with the old
         * values of 0.
         *
         * @param component component
         */
        @Override
        public void onRefreshed(Component component) {
            LogUtil.info(TAG, "Legend onRefreshed" + " getWidth: "
                    + component.getWidth() + " getHeight: " + component.getHeight());
            onLegendSizeChanged((int) mLegendWidth, (int) mLegendHeight, (int) mLegendWidth, (int) mLegendHeight);
        }


        /**
         * On touch event boolean
         *
         * @param component  component
         * @param touchEvent touch event
         * @return the boolean
         */
        @Override
        public boolean onTouchEvent(Component component, TouchEvent touchEvent) {
            LogUtil.info(TAG, "Legend onTouchEvent");
            return false;
        }
    }

}
